package net.pulseproject.router.app.misc;

import java.awt.Dimension;
import java.awt.Point;
import java.io.IOException;
import java.util.Collection;
import java.util.Properties;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import net.pulseproject.commons.midi.DeviceLister;
import net.pulseproject.commons.midi.collection.DeviceFilter;
import net.pulseproject.commons.midi.collection.InOutDevices;
import net.pulseproject.commons.midi.entity.Device;
import net.pulseproject.commons.midi.entity.MidiSocket;
import net.pulseproject.commons.midi.mediator.ActivatedRoutingTable;
import net.pulseproject.commons.midi.mediator.EnablerCollectionWrapper;
import net.pulseproject.commons.midi.mediator.Mediator;
import net.pulseproject.commons.midi.mediator.MediatorBuilder;
import net.pulseproject.commons.misc.binder.Binder;
import net.pulseproject.commons.misc.preferences.PreferencesField;
import net.pulseproject.commons.misc.preferences.PreferencesStore;
import net.pulseproject.commons.util.IoUtil;
import net.pulseproject.router.app.gui.Event;
import net.pulseproject.router.app.gui.Event.CloseConfigurationEvent;
import net.pulseproject.router.app.gui.Event.ExecuteConfigurationEvent;
import net.pulseproject.router.app.gui.Event.QuitEvent;
import net.pulseproject.router.app.model.Model;
import net.pulseproject.router.app.model.ModelField;
import net.pulseproject.router.app.view.MainWindow;
import net.pulseproject.router.app.view.WindowSize;
import net.pulseproject.router.config.parser.ConfigParser;
import net.pulseproject.router.config.parser.ConfigParserErrors;
import net.pulseproject.router.modules.misc.RoutingTable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.inject.Inject;

public class MainPresenterImpl implements MainPresenter {

	private static final Log LOG = LogFactory.getLog(MainPresenterImpl.class);
	
	private final MainWindow window;

	private final DeviceLister deviceLister;

	private final ConfigParser parser;

	private final MediatorBuilder mediatorBuilder;
	
	private final PreferencesStore store;
	
	private final Model model;
	
	private Mediator currentMediator;
	
	private final EnablerCollectionWrapper enablerCollectionWrapper;
	
	// TODO rewrite this stuff here with COMMAND PATTERN
	@Inject
	public MainPresenterImpl(final MainWindow window, final DeviceLister deviceLister, final ConfigParser parser,
			final MediatorBuilder mediatorBuilder, final PreferencesStore store, final Model model,
			final EnablerCollectionWrapper enablerCollectionWrapper) {
		LOG.debug("new MainPresenterImpl(window, deviceLister, parser, mediatorBuilder, store, model)");
		this.window = window;
		this.deviceLister = deviceLister;
		this.parser = parser;
		this.mediatorBuilder = mediatorBuilder;
		this.store = store;
		this.model = model;
		this.enablerCollectionWrapper = enablerCollectionWrapper;
		
		this.model.addBinding(new Binder<ModelField, MidiSocket>() {
			@Override public ModelField getField() {
				return ModelField.CURRENT_SOLOED_SOCKET;
			}
			@Override public void onValueSet(final Object value) {
				final MidiSocket socket;
				if(value == null) {
					socket = null;
				} else {
					socket = (MidiSocket) value;
				}
				MainPresenterImpl.this.onSetCurrentSoloedSocket(socket);
			}
		});
		
		this.window.addEventListener(this);
	}
	
	private void onSetCurrentSoloedSocket(final MidiSocket socket) {
		LOG.debug("onSetCurrentSoloedSocket(socket=" + socket + ") ... this.currentMediator=" + this.currentMediator);
		
		if(this.currentMediator == null) {
			// seems as MainPresenterImpl is currently closing the connection, nothing to do.
			return;
		}
		
		if(socket == null) {
			this.enablerCollectionWrapper.setAllEnabled();
		} else {
			this.enablerCollectionWrapper.setAllDisabledExcept(socket);
		}
	}
	
	
	@Override
	public final void startUp() {
		this.model.setConfigurationText(this.store.get(PreferencesField.RECENT_CONFIG_TEXT));
//		this.model.setPortFilter(this.store.get(PreferencesField.PORT_FILTER));
		
		try {
			final Properties props = IoUtil.loadPropertiesFromClassPath(this.getClass().getClassLoader(),
					"app.properties");
			final String versionString = (String) props.get("version");
			this.model.setVersionString(versionString);
		} catch (final IOException e) {
			throw new RuntimeException("Could not load properties!", e);
		}
		
		final Dimension windowDimension = this.store.get(PreferencesField.RECENT_WINDOW_DIMENSION);
		final Point windowLocation = this.store.get(PreferencesField.RECENT_WINDOW_POSITION);
		final WindowSize windowSize;
		if(windowDimension != null && windowLocation != null) {
			windowSize = new WindowSize(windowDimension, windowLocation);
		} else if(windowDimension == null && windowLocation == null) {
			windowSize = null;
		} else {
			throw new RuntimeException("Either window dimension and position are both null, or both not null, " +
					"but not mixed! windowDimension=" + windowDimension + ", windowLocation=" + windowLocation);
		}
		
        SwingUtilities.invokeLater(new Runnable() {
            @Override public void run() {
            	LOG.debug("onSwingInvokedLater()");
        		MainPresenterImpl.this.window.initComponents();
        		
        		MainPresenterImpl.this.window.updateWindowSize(windowSize);
        		MainPresenterImpl.this.window.setVisible(true);
        		
        		// listing midi devices takes a while, so do it afterwards
        		new Thread(new Runnable() {
        			@Override public void run() {
        				MainPresenterImpl.this.onAfterWindowVisibleThreadRunning();
        			}
        		}, "GUI-WorkerThread").start();
        }});
	}
	
	private void onAfterWindowVisibleThreadRunning() {
		LOG.debug("onAfterWindowVisibleThreadRunning()");
		final Collection<Device> allDevices = this.deviceLister.listDevices();
        
        final DeviceFilter filter = new DeviceFilter();
        final InOutDevices inOutDevices = filter.filterInOutDevices(allDevices);
        this.model.setAvailableDevices(inOutDevices);
	}

	@Override
	public final void onEventDispatched(final Event event) {
		if(event instanceof ExecuteConfigurationEvent) {
			final ExecuteConfigurationEvent executeEvent = (ExecuteConfigurationEvent) event;
			this.onExecuteConfiguration(executeEvent);

		} else if(event instanceof QuitEvent) {
			final QuitEvent quitEvent = (QuitEvent) event;
			this.onQuit(quitEvent);
			
		} else if(event instanceof CloseConfigurationEvent) {
			final CloseConfigurationEvent closeEvent = (CloseConfigurationEvent) event;
			this.onCloseConfiguration(closeEvent);
			
			
		} else {
			LOG.warn("Unhandled event dispatched: " + event);
		}
	}
	
	private void onCloseConfiguration(final CloseConfigurationEvent closeEvent) {
		LOG.debug("onCloseConfiguration(closeEvent=" + closeEvent + ")");
		
		this.closeMediator();
	}
	
	private void closeMediator() {
		try {
			this.currentMediator.close();
		} catch (final IOException e) {
			throw new RuntimeException("Could not close mediator!", e);
		} finally {
			this.currentMediator = null;
			
			this.model.setActiveOutgoingSockets(null);
			this.model.setCurrentSoloedSocket(null);
			this.model.setRoutingActive(Boolean.FALSE);
		}
	}

	private void onQuit(final QuitEvent quitEvent) {
		LOG.debug("onQuit(quitEvent=" + quitEvent + ")");
		
		this.quit();
	}
	
	@Override
	public final void quit() {
		LOG.info("quit()");
		
		if(this.currentMediator != null) {
			this.closeMediator();
		}
		
		this.store.set(PreferencesField.RECENT_CONFIG_TEXT, this.model.getConfigurationText());
//		this.store.set(PreferencesField.PORT_FILTER, this.model.getPortFilter());
		
		final Dimension recentWindowSize = this.window.getSize();
		final Point recentWindowPosition = this.window.getLocation();
		this.store.set(PreferencesField.RECENT_WINDOW_DIMENSION, recentWindowSize);
		this.store.set(PreferencesField.RECENT_WINDOW_POSITION, recentWindowPosition);
		
		this.store.flush();
		
		LOG.trace("disposing window.");
		this.window.dispose();
	}
	
	private void onExecuteConfiguration(final ExecuteConfigurationEvent event) {
		LOG.debug("onExecuteConfiguration(event)");
		
		final String rawText = event.getRawText();
		final ConfigParserErrors errors = new ConfigParserErrors();
		final RoutingTable table = this.parser.parse(rawText, errors); // result could be null
		if(errors.isError()) {
			JOptionPane.showMessageDialog(null, "Following error(s) occured:\n" + errors.prettyPrint(), "Invalid Configuration", JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		this.currentMediator = this.mediatorBuilder.build(table);
		final ActivatedRoutingTable activatedTable = this.currentMediator.activate();
		
		this.model.setActiveOutgoingSockets(activatedTable.getOutgoingSockets());
		this.model.setRoutingActive(Boolean.TRUE);
	}
}
